package featuresProcessing;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.features2d.DMatch;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;

import utils.Species;

/**
 * Implementation of the Detector interface, using the SIFT algorithm in order to extract keypoint from input images
 * and compute descriptors for each keypoint. The classification is done by comparing these descriptors with the ones
 * of the query image. The ranking is created by considering the number of found matchings for each leaf. So, the first ranked leaf
 * is the one having the greatest number of key points that match the query.
 *
 */
public class FeaturesDetector implements Detector {

	private FeatureDetector detector;
	private DescriptorExtractor descriptorExtractor;
	private DescriptorMatcher matcher;


	public FeaturesDetector(int detectorType, int descriptorType, int matcherType, String configFilePath) { 
		detector = FeatureDetector.create(detectorType);
		descriptorExtractor = DescriptorExtractor.create(descriptorType);
		matcher = DescriptorMatcher.create(matcherType);
		detector.read(configFilePath);
	}


	public Mat getDescriptors(Mat frame){
		
		MatOfKeyPoint keyPoints =  new MatOfKeyPoint();
		Mat descriptors = new Mat();
		
		detector.detect(frame, keyPoints);
		descriptorExtractor.compute(frame, keyPoints, descriptors);
		return descriptors;
	}    


	@Override
	public void addTrainingDescriptors (Collection<Species> species) {
		ArrayList<Mat> descriptors = new ArrayList<Mat>();
		Iterator<Species> it = species.iterator();
		while(it.hasNext()) {
			Mat next = it.next().getFeatures();
			descriptors.add(next);
		}
		matcher.add(descriptors);
	}

	@Override
	public HashMap<Integer, Float> classify (Mat descriptors) throws EmptyClassifierException {
		if (matcher.getTrainDescriptors().size() == 0)
			throw new EmptyClassifierException("You must fill the training set before classifying.");
		
	
		
		MatOfDMatch matches = new MatOfDMatch();
		matcher.match(descriptors, matches);
		List<DMatch> matchesList = matches.toList();
		matchesList = findGoodMatches(matchesList);
		matches.release();
		
		HashMap<Integer, Float> distances = new HashMap<Integer, Float>();
		for (int i = 0; i < matchesList.size(); i++) {
			DMatch tmp = matchesList.get(i);
			if (distances.get(tmp.imgIdx)==null)
				distances.put(tmp.imgIdx, 1f);
			else
				distances.put(tmp.imgIdx, distances.get(tmp.imgIdx)+1);
		}
		
		Iterator<Integer> it = distances.keySet().iterator();
		while (it.hasNext()) {
			Integer currKey = it.next();
			distances.put(currKey, 1-(distances.get(currKey)/matchesList.size()));
		}
		
		return distances;
	}

	  private List<DMatch> findGoodMatches(List<DMatch> matchesList){
	    	float minimumDist = 100;
	    	float maximumDist = 0;
	    	
	    	for (int i = 0; i < matchesList.size(); i++) {
	    		float dist = matchesList.get(i).distance;
				if (dist > maximumDist){
					maximumDist = matchesList.get(i).distance;
				}
				if (dist < minimumDist){
					minimumDist = matchesList.get(i).distance;
				}
			}
	    	List<DMatch> goodMatchesList = new ArrayList<DMatch>();
	    	for (int i = 0; i < matchesList.size(); i++) {
				if (matchesList.get(i).distance <= 3 * minimumDist){
					goodMatchesList.add(matchesList.get(i));
				}
			}
			return goodMatchesList;
	    }
}
